package com.czk.bootlettuce.reentrantredislock;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author chenzhangkun
 * @ClassName ReentrantDistributedLock
 * @description: TODO
 * @date 2024年02月01日
 */
public class ReentrantDistributedLock implements Lock {
    /**
     * 锁超时时间
     */
    protected long internalLockLeaseTime = 3000;
    /**
     * 客户端的唯一标识
     */
    private final String id = UUID.randomUUID().toString();
    /**
     * 资源名称
     */
    private String resourceName;

    private final List<String> keys = new ArrayList<>(1);

    private final StringRedisTemplate redisTemplate;

    public ReentrantDistributedLock(String resourceName, StringRedisTemplate redisTemplate) {
        this.resourceName = resourceName;
        this.redisTemplate = redisTemplate;
        keys.add(resourceName);
    }

    @Override
    public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
        // 转为毫秒
        long time = unit.toMillis(waitTime);
        long current = System.currentTimeMillis();
        // 线程id
        long threadId = Thread.currentThread().getId();
        // 获取锁
        Long tt1 = tryAcquire(leaseTime, unit, threadId);
        if (tt1 == null) {
            return true;
        }
        time -= System.currentTimeMillis() - current;
        if (time <= 0) {
            return false;
        }
        // 自旋
        while (true) {
            long currentTime = System.currentTimeMillis();
            tt1 = tryAcquire(leaseTime, unit, threadId);
            if (tt1 == null) {
                return true;
            }
            time -= System.currentTimeMillis() - currentTime;
            if (time <= 0) {
                return false;
            }
        }
    }

    @Override
    public void lock(long leaseTime, TimeUnit unit) {
        // 线程id
        long threadId = Thread.currentThread().getId();
        Long ttl = tryAcquire(leaseTime, unit, threadId);
        if (ttl == null) {
            return;
        }
        do {
            ttl = tryAcquire(leaseTime, unit, threadId);
        } while (ttl != null);
    }

    @Override
    public void unLock() {
        long threadId = Thread.currentThread().getId();
        DefaultRedisScript<Long> redisScript=new DefaultRedisScript<>(LuaScript.reentrantUnlockScript(),Long.class);
        Long opStatus = redisTemplate.execute(redisScript, keys, String.valueOf(internalLockLeaseTime), getRequestId(threadId));
        if (opStatus==null){
            throw new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
                    + id + " thread-id: " + threadId);
        }
    }

    private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(LuaScript.reentrantLockScript(), Long.class);
        return redisTemplate.execute(redisScript, keys, String.valueOf(unit.toMillis(leaseTime)), getRequestId(threadId));
    }

    private String getRequestId(long threadId) {
        return id + ":" + threadId;
    }
}
